<script setup lang="ts">
import { computed, nextTick, onMounted, onUpdated, ref } from "vue";

interface IDatas {
  id: number | string;
  [propname: string]: any;
}

type TypePosition = {
  index: number;
  height: number;
  top: number;
  bottom: number;
}

const props = defineProps<{
  datas: IDatas[]; // 列表元素
  size: number; // 一个列表元素高度
  remain: number; // 展示几个列表元素
  variable?: boolean; // 列表元素高度不固定
}>();

const startIndex = ref<number>(0);
const endIndex = ref<number>(props.remain);
const offset = ref<number>(0);
const viewport = ref<HTMLElement>();
const scrollBar = ref<HTMLElement>();
const scrollElements = ref<HTMLElement[]>([]);

let positions: TypePosition[] = []

// 缓存当前元素的高度，top、bottom
// DOM渲染后，更新实际的高度，top、bottom
const cacheList = () => {
  positions = props.datas.map((_, index) => ({
    index,
    height: props.size,
    top: index * props.size,
    bottom: (index + 1) * props.size
  }))
}

const init = () => {
  if (props.variable) {
    cacheList()
  }
}

init()



const getStartIndex = (value: number): number => {
  let start = 0;
  let end = positions.length;
  let temp = 0;
  while (start < end) {
    let middleIndex = parseInt(String((start + end) / 2));
    let middleValue = positions[middleIndex].bottom;
    if (value == middleValue) {
      return middleIndex + 1;
    } else if (middleValue < value) {
      start = middleIndex + 1;
    } else if (middleValue > value) {
      if (temp == 0 || temp > middleIndex) {
        temp = middleIndex;
      }
      end = middleIndex - 1;
    }
  }
  return temp;
}

const handleScroll = (time: number) => {
  let timer: any;
  return () => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      const scrollTop = viewport.value?.scrollTop || 0;
      if (props.variable) {
        startIndex.value = getStartIndex(scrollTop);
        endIndex.value = startIndex.value + props.remain;
        // 超出预留区域才需要偏移
        offset.value = positions[startIndex.value - preIndex.value].top;
      } else {
        startIndex.value = Math.floor(scrollTop / props.size);
        endIndex.value = startIndex.value + props.remain;
        // 超出预留区域才需要偏移
        offset.value = startIndex.value * props.size - preIndex.value * props.size;
      }

    }, time);
  };
};
const debounce = handleScroll(0);

onMounted(() => {
  nextTick(() => {
    if (viewport.value && scrollBar.value && scrollBar.value) {
      viewport.value.style.height = `${props.size * props.remain}px`; // 视图高度
      scrollBar.value.style.height = `${props.size * props.datas.length}px`; // 滚动条的高度
    }

  });
});


onUpdated(() => {
  nextTick(() => {
    scrollElements.value.forEach((element) => {
      const { height } = element.getBoundingClientRect()
      const index = Number(element.getAttribute('vid'))
      let oldHeight = positions[index].height;
      let gapHeight = oldHeight - height;
      if (gapHeight) {
        positions[index].height = height;
        positions[index].bottom = positions[index].bottom - gapHeight;
        for (let i = index + 1; i < positions.length; i++) {
          positions[i].top = positions[i - 1].bottom;
          positions[i].bottom = positions[i].bottom - gapHeight;
        }
      }
    })
    // 动态计算滚动条高度
    if (scrollBar.value) {
      scrollBar.value.style.height = positions[positions.length - 1].bottom + 'px';
    }
  })

})

// 预渲染区域防止白屏
const preIndex = computed(() => {
  return Math.min(startIndex.value, props.remain);
});

// 预渲染区域
const nextIndex = computed(() => {
  return Math.min(props.datas.length - endIndex.value, props.remain);
});

// 格式化数据，用于元素不定高时，查找更新真实的DOM信息
const formatData = computed(() => {
  return props.datas.map((item, index) => ({ ...item, index }))
});

// 实例渲染的列表
const visibleList = computed(() => {
  return formatData.value.slice(
    startIndex.value - preIndex.value,
    endIndex.value + nextIndex.value
  );
});
</script>

<template>
  <div class="viewport" ref="viewport" @scroll="debounce">
    <div class="scroll-bar" ref="scrollBar"></div>
    <div class="scroll-list" :style="{ transform: `translate3d(0,${offset}px,0)` }">
      <div v-for="_ in visibleList" :key="_.id" :vid="_.id" ref="scrollElements">
        <slot :_="(_ as IDatas)"></slot>
      </div>

    </div>
  </div>
</template>

<style scoped>
.viewport {
  overflow-y: scroll;
  position: relative;
}

.scroll-list {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}
</style>
